# from my_dbg import *
from Dir_tree import Dir_tree
class Class_node:
    def __init__(self, line, deco_list):
        self.name = self.get_name(line)
        self.line = line
        self.func_dict = {}
        self.deco_list = deco_list

        self.opt_list = []
        self.diff_list = []
    def __str__(self):
        r_list = ["===str==="] + [self.line]
        r_list += ["func_dict"] + [str(self.func_dict)]
        r_list += ["deco_list"] + self.deco_list
        r_list += ["opt_list"] + self.opt_list
        r_list += ["==========="]
        return "\n".join(r_list)
    def merge(self, that_class_node):
        if self.name != that_class_node.name:
            raise "name{self_name} is different from that_name:{that_name}".format(self_name=self.name, that_name=that_class_node.name)
        
        for func_node in that_class_node.func_dict.values():
            self.func_dict[func_node.name] = func_node
        self.opt_list += that_class_node.opt_list
        self.deco_list += that_class_node.deco_list
        self.diff_list += that_class_node.diff_list


    def get_name(self, line): return line.split("class ", 1)[1].split(":", 1)[0].strip()

    def sum_diff(self, that_node):
        self.diff_list = []
        for func_node in self.func_dict.values():
            that_func_node = None
            if func_node.name in that_node.func_dict:
                that_func_node = that_node.func_dict[func_node.name]
            func_node.sum_diff(that_func_node)
            self.diff_list.append(func_node.diff)
        for func_name in that_node.func_dict:
            if func_name not in self.func_dict:
                that_func_node = that_node.func_dict[func_name]
                that_func_node.sum_diff(None)
                self.diff_list.append(that_func_node.diff)
class Sum_diff:
    def __init__(self, class_name, func_name, sum_src, sum_dst):
        self.class_name = class_name
        self.func_name = func_name
        self.sum_src = sum_src
        self.sum_dst = sum_dst
        self.src_name = "src"
        self.dst_name = "dst"
    def __str__(self):
        src_name, dst_name = self.src_name, self.dst_name
        class_name, func_name = self.class_name, self.func_name
        sum_src, sum_dst = self.sum_src, self.sum_dst
        sum_src = "\n".join(sum_src)
        sum_dst = "\n".join(sum_dst)
        diff_str = "[{class_name}] <{func_name}> \n|> {src_name} <|\n{sum_src}\n\n|> {dst_name} <|\n{sum_dst}\n".format(**locals())
        same_str = ""
        if sum_src == sum_dst:
            return same_str
        return diff_str
    def diff_str(self, src_name, dst_name):
        self.src_name = src_name
        self.dst_name = dst_name
        return str(self)

class Func_node:
    def __init__(self, line, deco_list, class_name):
        self.class_name = class_name
        self.name = self.get_name(line)
        self.line = line
        self.code_list = []
        self.sum_list = []
        
        self.deco_list = deco_list

        self.diff = None

    
    def get_name(self, line): return line.split(" def ", 1)[1].split("(", 1)[0].strip()

    def sum_diff(self, that_node): 
        that_sum_list = []
        if that_node != None:
            that_sum_list = that_node.sum_list
        self.diff = Sum_diff(self.class_name, self.name, self.sum_list, that_sum_list)
        return self.diff
    
    def get_summary(self):    
        self.sum_list = [self.line]
        for line in self.code_list:
            line = self.extract_line(line)
            if line.strip() != "":
                self.sum_list.append(line)
    
        # self.sum_list = [self.extract_line(line) for line in self.code_list]
        # self.sum_list = list(filter(None, self.sum_list))

    def extract_line(self, line):
        if "#" in line and ":" in line:
            return line
        if "class " in line:
            return line
        if "for" in line and "in" in line and ":" in line:
            return line
        if " while " in line and ":" in line:
            return line
        if " if " in line and ":" in line:
            return line
        if " return " in line:
            return line
        if " raise " in line:
            return line 
        if " self." in line and "=" in line and '"' in line:
            return line 
        if " break" in line or " continue" in line:
            return line 
        return ""

class Pyc_parser:
    def __init__(self):
        self.project = {}
        #self.class_list = None
        self.write_sum = False

        self.str_mode_flag = False
    # # init from str
    # def str_mode(self):        self.str_mode_flag = True; return self
    # def file_mode(self):  self.str_mode_flag = False; return self

    def __lshift__(self, relate_path):
        self.parse_pyc(relate_path)

    def __rshift__(self, relate_path):
        if self.write_sum == True:
            self.write_pyc_sum(relate_path)
        else:
            self.write_pyc(relate_path)
    
    def from_pyc_line_list(self, pyc_list):
        

        # switch empty line
        # 如果前后两行 之间是8空格， 那么本行是空注释
        # pyc_lines = []
        # space_num = lambda s, n: n if len(s)==0 or s[0] != " " else space_num(s[1:], n+1)
        # for i, line in enumerate( pyc_list ):
            
        #     if line.strip() == "":
        #         # 空行的space数量和上一行相同。
        #         if len(pyc_lines) == 0:  continue
        #         empty_line = " "* space_num(pyc_lines[-1], 0) + '"////"'
        #         pyc_lines.append(empty_line)
        #     else:
        #         pyc_lines.append(line)

        
        ##############################dbg
        # if not "###########debug":
        #     log_list = []
        #     for i,line in enumerate( pyc_lines ):
        #         log_list.append("[{i}] {line}".format(**locals()))
        #     Dir_tree()/"log_info" + log_list
        # kill empty line
        pyc_lines = [line for line in pyc_list if line.strip() != ""]
        # kill comment
        pyc_lines = [line for line in pyc_lines if not line.strip().startswith("#")]
        
        # class func and func_code
        r_dict = self.project
        cur_class = None
        cur_func = None
        cur_code_list = None
        deco_list = []
        cur_father = None
        for i, line in enumerate( pyc_lines ):
            #print("[{i}] {line}".format(**locals()))
            #OO_info(line)
            # class
            if line.startswith("class "):
                cur_class = Class_node(line, deco_list)
                deco_list = []
                cur_code_list = None
                cur_class_name = cur_class.name
                if cur_class_name not in r_dict:
                    r_dict[cur_class.name] = cur_class
                else:
                    r_dict[cur_class.name].merge(cur_class)
                    cur_class = r_dict[cur_class.name]
                cur_father = cur_class
                continue
            # func
            if line.startswith(" "*4 + "def "):
                cur_func = Func_node(line, deco_list, cur_class.name)
                deco_list  = []
                cur_class.func_dict[cur_func.name] = cur_func
                cur_father = cur_func
                cur_code_list = cur_func.code_list
                continue
            if line.strip().startswith("@"):
                deco_list.append(line)
                cur_code_list = "last_deco"
                continue
            # comment
            if cur_code_list == "last_deco":
                deco_list[-1] = deco_list[-1] + "\n" + line
                continue
            if line.startswith(" "*8):
                if cur_code_list != None:
                    cur_code_list.append(line)
                continue
            # option
            if cur_class != None:
                cur_class.opt_list.append(line)
        for func_node in self.func_list():
            func_node.get_summary()
        
        # if not "#########debug":
        #     for line in cur_class.opt_list:
        #         print(line)
        # return r_dict

    def parse_pyc(self, pyc_relate_path):
        pyc = Dir_tree()/pyc_relate_path
        pyc_lines = list(pyc)
        self.from_pyc_line_list(pyc_lines)

    def to_pyc_line_list(self):
        pyc_code_list = []
        pyc_dict = self.project
        for class_node in pyc_dict.values():
            pyc_code_list += class_node.deco_list
            pyc_code_list.append(class_node.line)
            for func_node in class_node.func_dict.values():
                pyc_code_list += func_node.deco_list
                pyc_code_list.append(func_node.line)
                pyc_code_list += func_node.code_list
        return pyc_code_list
    # 先实现一个不排序的版本
    def write_pyc(self, pyc_relate_path):
        pyc = Dir_tree()/pyc_relate_path
        pyc << self.to_pyc_line_list

    def write_pyc_sum(self, pyc_relate_path):
        # hide
        for func_node in self.func_list():
            func_node.sum_list = [func_node.sum_list, func_node.deco_list, func_node.code_list]
            func_node.code_list = func_node.sum_list[0]
            func_node.deco_list = []
        # write
        self.write_pyc(pyc_relate_path)
        # recover
        for func_node in self.func_list():
            func_node.sum_list, func_node.deco_list, func_node.code_list = func_node.sum_list

    def func_node(self, class_name, func_name):
        return self.project[class_name].func_dict[func_name]
    
    def sum_diff(self, that):
        for class_node in self.class_list():
            class_name = class_node.name
            that_class_node = None
            if class_name in that.project:
                that_class_node = that.project[class_name]
            class_node.sum_diff(that_class_node)
    def func_list(self):
        r_list = []
        for class_node in self.class_list():
            for func_node in class_node.func_dict.values():
                r_list.append(func_node)
        return r_list
    def class_list(self):
        return self.project.values()
    
    def show_diff(self, that, src_name, dst_name):
        OO_info("====NO DIFF FLAG====")
        self.sum_diff(that)
        for class_node in self.class_list():
            for diff in class_node.diff_list:
                diff_str = diff.diff_str(src_name, dst_name)
                if diff_str != "":
                    OO_info(diff)

if __name__ == "__main__":

    a = Pyc_parser()
    b = Pyc_parser()
    src = "_src_code.py"
    dst = "0_py_c/L_chd/Project_proc.py"
    mid = "_mid_plan.py"

    import datetime
    def next():
        a << src
        a >> src + datetime.datetime.now().strftime("_%Y.%m.%d_%H_%M_%S")
        b << dst
        b >> src

    def write_mid():
        a << src
        a.write_sum = True
        a >> mid

    def diff_src():
        a << src
        b << mid
        a.show_diff(b, "src", "mid")

    def diff_dst():
        a << mid
        b << dst
        a.show_diff(b, "mid", "dst")

    import sys
    eval(sys.argv[1]+"()")
